◆自己紹介
MS事業部データマイニング推進部 ミシェルです。

◆SIGNATEとは
企業が問題解決のためにデータを提供し、エンジニアがデータを用いて
予測モデルを作成し精度を競い合うコンペの開催など行われるデータ分析のコミュニティ。

◆予測モデルとは
機械学習アルゴリズム(データを処理するアルゴリズム)が
大量のデータの中から関係性を見つけだし、計算式の塊を作る。
この計算式の塊を予測モデルと呼び、ここに新たなデータを当てはめることで予測が出来る。

コンペ説明

◆コンペ概要
ある会社のカフェフロアで販売されているお弁当の売上数を予測するモデルを作成する。
曜日やメニュー等の複数の変数から最適なお弁当の販売量を予測し、お弁当の売上向上・廃棄削減につなげる。

◆教師あり学習
正解となるデータが存在する場合に用いられる手法。

・教師あり学習で解ける問題は大きく分けて「分類」と「回帰」がある。
分類:「動物の種類」や「YesかNoか」などデータが属するクラスを予測する問題
回帰:「年齢」や「金額」などの連続量、数値を予測する問題

今回は、お弁当の売上数を予測する問題なので回帰問題である。

◆データ
データ期間:お弁当の販売を開始した 2013年11月18日 から 2014年11月30日 まで(土日祝を除く平日)(247日)
学習用データ期間:2013年11月18日 ~ 2014年 9月30日
検証用データ期間:2014年10月 1日 ~ 2014年11月30日

◆評価指標
・モデル精度の評価は、評価関数「RMSE(Root Mean Squared Error 平均二乗平方根誤差)」を使用する。
・予測値が正解とどれくらい乖離しているか示すもので、値が小さいほど精度が高いと判断する。

◆分析の手順
①前処理(欠損処理など学習しやすくするためのデータの加工)
②モデル作成(パラメータチューニングなど)
③評価

データの読み込み

In [1]:
import pandas as pd
import gc
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import KFold,cross_val_score,GridSearchCV
from matplotlib import pyplot as plt
import seaborn as sns
sns.set(font="IPAexGothic",style="white")
from sklearn.metrics import mean_squared_error as MSE
from sklearn.preprocessing import StandardScaler
import numpy as np
from IPython.display import Image, display_png
In [2]:
# 現在の最大表示列数の出力
pd.get_option("display.max_columns")
# 最大表示列数の指定(ここでは50列を指定)
pd.set_option('display.max_columns', 50)

日付のカラムをインデックスとして取り込み

In [3]:
train = pd.read_csv('./data/train.csv',index_col=['datetime'])
test = pd.read_csv('./data/test.csv',index_col=['datetime'])

00 datetid(datetime):インデックスとして使用する日付(yyyy-m-d)
01 y(int):販売数(目的変数)
</div> 02 week(char):曜日(月~金)
03 soldout(boolean):完売フラグ(0:完売せず、1:完売)
04 name(varchar):メインメニューの名前
05 kcal(int):おかずのカロリー
06 remarks(varchar):特記事項
07 event(varchar):13時開始お弁当持ち込み可の社内イベント
08 payday(boolean):給料日フラグ(1:給料日)
09 weather(varchar):天気
10 precipitation(float):降水量。ない場合は "--"
11 temperature(float):気温

In [4]:
train.tail(20)
Out[4]:
y week soldout name kcal remarks event payday weather precipitation temperature
datetime
2014-9-1 65 1 ビーフシチュー 380.0 NaN NaN NaN -- 23.4
2014-9-2 68 1 名古屋味噌カツ 440.0 手作りの味 NaN NaN 晴れ -- 29.1
2014-9-3 53 0 親子煮 408.0 NaN NaN NaN -- 26.7
2014-9-4 54 1 チキンステーキ・きのこソース 405.0 NaN NaN NaN 0 26.5
2014-9-5 43 0 メンチカツ 380.0 NaN NaN NaN 晴れ -- 30.2
2014-9-8 68 1 鶏肉の山賊焼き 385.0 NaN NaN NaN -- 23.1
2014-9-9 63 0 ハンバーグデミソース 460.0 NaN NaN NaN 晴れ -- 26.8
2014-9-10 54 0 ぶりレモンペッパー焼き 450.0 NaN NaN 1.0 0 25.0
2014-9-11 53 0 手作りチキンカツ 385.0 NaN NaN NaN -- 21.9
2014-9-12 115 0 ポークカレー NaN お楽しみメニュー NaN NaN 晴れ -- 27.3
2014-9-16 56 0 チーズメンチカツ 438.0 NaN NaN NaN 晴れ -- 28.8
2014-9-17 49 0 チキンフリカッセ 430.0 料理長のこだわりメニュー ママの会 NaN -- 25.0
2014-9-18 46 0 カレイ唐揚げ 甘酢あん 395.0 NaN NaN NaN -- 24.1
2014-9-19 45 0 厚切イカフライ 400.0 NaN NaN NaN 薄曇 -- 23.6
2014-9-22 29 0 筑前煮 395.0 NaN NaN NaN 晴れ -- 25.2
2014-9-24 59 1 白身魚のマスタード焼き 408.0 NaN NaN NaN 0 24.8
2014-9-25 50 0 牛カルビ焼き肉 394.0 NaN NaN NaN 0 25.4
2014-9-26 45 0 ランチビュッフェ NaN スペシャルメニュー(800円) キャリアアップ支援セミナー NaN 晴れ -- 27.1
2014-9-29 56 1 豚肉と玉子の炒め 404.0 NaN NaN NaN 快晴 -- 26.6
2014-9-30 40 0 鶏肉とカシューナッツ炒め 398.0 NaN NaN NaN 快晴 -- 28.1
In [5]:
test.head()
Out[5]:
week soldout name kcal remarks event payday weather precipitation temperature
datetime
2014-10-1 1 メンチカツ 420.0 NaN NaN NaN 0 20.2
2014-10-2 0 バーベキューチキン 415.0 NaN NaN NaN -- 23.9
2014-10-3 0 豚肉のマスタード焼き 405.0 NaN NaN NaN 晴れ -- 28.7
2014-10-6 1 麻婆春雨 400.0 NaN NaN NaN 0.5 21.5
2014-10-7 0 厚揚げ肉みそ炒め 430.0 NaN NaN NaN 晴れ -- 22.1
In [6]:
# (行数,カラム数)
train.shape
Out[6]:
(207, 11)
In [7]:
# (行数,カラム数)
test.shape
Out[7]:
(40, 10)

trainデータとtestデータを結合

trainデータとtestデータ両方に前処理を適用させるため、結合する。
モデリング前にtrainデータとtestデータに分けられるようにフラグを立てておく。

In [8]:
# フラグを立てる
train['flg'] = 1
test['flg'] = 0
# trainデータとtestデータを結合
all_data = pd.concat([train,test], axis=0, sort=False)
In [9]:
# 欠損数、型確認
all_data.info()
<class 'pandas.core.frame.DataFrame'>
Index: 247 entries, 2013-11-18 to 2014-11-28
Data columns (total 12 columns):
y                207 non-null float64
week             247 non-null object
soldout          247 non-null int64
name             247 non-null object
kcal             202 non-null float64
remarks          28 non-null object
event            17 non-null object
payday           12 non-null float64
weather          247 non-null object
precipitation    247 non-null object
temperature      247 non-null float64
flg              247 non-null int64
dtypes: float64(4), int64(2), object(6)
memory usage: 25.1+ KB
In [10]:
# 各カラムの統計量を見る
all_data.describe()
Out[10]:
y soldout kcal payday temperature flg
count 207.000000 247.000000 202.000000 12.0 247.000000 247.000000
mean 86.623188 0.445344 407.381188 1.0 19.157085 0.838057
std 32.882448 0.498013 28.396942 0.0 8.075680 0.369147
min 29.000000 0.000000 315.000000 1.0 1.200000 0.000000
25% 57.000000 0.000000 395.000000 1.0 13.650000 1.000000
50% 78.000000 0.000000 412.000000 1.0 19.400000 1.000000
75% 113.000000 1.000000 427.000000 1.0 25.450000 1.000000
max 171.000000 1.000000 462.000000 1.0 34.600000 1.000000

前処理

欠損処理

欠損値があると解析が難しくなるため、除去または補完する。

◆kcal(カロリー)
・2013-12-26までカロリーを記録していない

In [11]:
all_data.loc[all_data['kcal'].isnull()  , ['week', 'kcal','y']]
Out[11]:
week kcal y
datetime
2013-11-18 NaN 90.0
2013-11-19 NaN 101.0
2013-11-20 NaN 118.0
2013-11-21 NaN 120.0
2013-11-22 NaN 130.0
2013-11-25 NaN 135.0
2013-11-26 NaN 145.0
2013-11-27 NaN 140.0
2013-11-28 NaN 151.0
2013-11-29 NaN 116.0
2013-12-2 NaN 151.0
2013-12-3 NaN 153.0
2013-12-4 NaN 151.0
2013-12-5 NaN 171.0
2013-12-6 NaN 134.0
2013-12-9 NaN 165.0
2013-12-10 NaN 155.0
2013-12-11 NaN 157.0
2013-12-12 NaN 109.0
2013-12-13 NaN 111.0
2013-12-16 NaN 160.0
2013-12-17 NaN 145.0
2013-12-18 NaN 145.0
2013-12-19 NaN 151.0
2013-12-20 NaN 134.0
2013-12-24 NaN 122.0
2013-12-25 NaN 121.0
2013-12-26 NaN 80.0
2014-3-28 NaN 106.0
2014-4-11 NaN 128.0
2014-4-25 NaN 80.0
2014-5-16 NaN 126.0
2014-5-30 NaN 119.0
2014-6-13 NaN 121.0
2014-6-27 NaN 74.0
2014-7-11 NaN 124.0
2014-7-25 NaN 83.0
2014-8-8 NaN 129.0
2014-8-22 NaN 100.0
2014-9-12 NaN 115.0
2014-9-26 NaN 45.0
2014-10-10 NaN NaN
2014-10-24 NaN NaN
2014-11-7 NaN NaN
2014-11-21 NaN NaN

・2013-12-26以降、「お楽しみメニュー」の日は取ってない

In [12]:
all_data.loc[all_data['week'] == '金'  , ['week', 'kcal', 'remarks','y']]
Out[12]:
week kcal remarks y
datetime
2013-11-22 NaN NaN 130.0
2013-11-29 NaN NaN 116.0
2013-12-6 NaN NaN 134.0
2013-12-13 NaN NaN 111.0
2013-12-20 NaN NaN 134.0
2014-1-10 440.0 NaN 87.0
2014-1-17 430.0 NaN 85.0
2014-1-24 418.0 鶏のレモンペッパー焼(50食)、カレー(42食) 92.0
2014-1-31 460.0 NaN 92.0
2014-2-7 425.0 NaN 91.0
2014-2-14 432.0 NaN 86.0
2014-2-21 410.0 酢豚(28食)、カレー(85食) 113.0
2014-2-28 428.0 NaN 69.0
2014-3-7 396.0 NaN 73.0
2014-3-14 360.0 NaN 70.0
2014-3-28 NaN お楽しみメニュー 106.0
2014-4-4 350.0 NaN 90.0
2014-4-11 NaN お楽しみメニュー 128.0
2014-4-18 325.0 NaN 56.0
2014-4-25 NaN お楽しみメニュー 80.0
2014-5-2 420.0 NaN 47.0
2014-5-9 434.0 NaN 58.0
2014-5-16 NaN お楽しみメニュー 126.0
2014-5-23 400.0 NaN 65.0
2014-5-30 NaN お楽しみメニュー 119.0
2014-6-6 430.0 NaN 93.0
2014-6-13 NaN お楽しみメニュー 121.0
2014-6-20 407.0 NaN 57.0
2014-6-27 NaN お楽しみメニュー 74.0
2014-7-4 432.0 NaN 63.0
2014-7-11 NaN お楽しみメニュー 124.0
2014-7-18 401.0 NaN 55.0
2014-7-25 NaN お楽しみメニュー 83.0
2014-8-1 380.0 NaN 38.0
2014-8-8 NaN お楽しみメニュー 129.0
2014-8-22 NaN お楽しみメニュー 100.0
2014-8-29 398.0 NaN 39.0
2014-9-5 380.0 NaN 43.0
2014-9-12 NaN お楽しみメニュー 115.0
2014-9-19 400.0 NaN 45.0
2014-9-26 NaN スペシャルメニュー(800円) 45.0
2014-10-3 405.0 NaN NaN
2014-10-10 NaN お楽しみメニュー NaN
2014-10-17 415.0 NaN NaN
2014-10-24 NaN お楽しみメニュー NaN
2014-10-31 436.0 NaN NaN
2014-11-7 NaN お楽しみメニュー NaN
2014-11-14 427.0 NaN NaN
2014-11-21 NaN お楽しみメニュー NaN
2014-11-28 416.0 NaN NaN

・大きくずれた値がないので平均値で埋める

In [13]:
all_data['kcal'].describe()
Out[13]:
count    202.000000
mean     407.381188
std       28.396942
min      315.000000
25%      395.000000
50%      412.000000
75%      427.000000
max      462.000000
Name: kcal, dtype: float64
In [14]:
all_data['kcal'] = all_data['kcal'].fillna(train['kcal'].mean())
all_data['kcal'].head()
Out[14]:
datetime
2013-11-18    404.409639
2013-11-19    404.409639
2013-11-20    404.409639
2013-11-21    404.409639
2013-11-22    404.409639
Name: kcal, dtype: float64

◆payday(給料日フラグ(1:給料日))
・給料日は月一

In [15]:
print(train.payday.unique())
all_data.loc[~all_data['payday'].isnull() , ['week', 'payday']]
[nan  1.]
Out[15]:
week payday
datetime
2013-12-10 1.0
2014-1-10 1.0
2014-2-10 1.0
2014-3-10 1.0
2014-4-10 1.0
2014-5-9 1.0
2014-6-10 1.0
2014-7-10 1.0
2014-8-8 1.0
2014-9-10 1.0
2014-10-10 1.0
2014-11-10 1.0

・給料日=1なので、nanは0で埋める

In [16]:
all_data['payday'] = all_data['payday'].fillna(0)
all_data['payday'].unique()
Out[16]:
array([0., 1.])

◆event(13時開始お弁当持ち込み可の社内イベント)

In [17]:
all_data['event'] = all_data['event'].fillna('なし')
all_data['event'].unique()
Out[17]:
array(['なし', 'ママの会', 'キャリアアップ支援セミナー'], dtype=object)

◆remarks(お楽しみメニューなど特記事項の有無)

目的変数と説明変数の関係を箱ひげ図で確認(カテゴリ変数のみ)
顕著に売上数に反応しているのはremarksの値がお楽しみメニューである時

In [18]:
fig, ax = plt.subplots(2,2,figsize=(12,7))
sns.boxplot(x="week",y="y",data=train,ax=ax[0][0])
sns.boxplot(x="weather",y="y",data=train,ax=ax[0][1])
sns.boxplot(x="remarks",y="y",data=train,ax=ax[1][0])
ax[1][0].set_xticklabels(ax[1][0].get_xticklabels(),rotation=30)
sns.boxplot(x="event",y="y",data=train,ax=ax[1][1])
plt.tight_layout()

お楽しみメニューでないときは「なし」で埋める

In [19]:
all_data.loc[all_data['remarks'] != 'お楽しみメニュー', 'remarks'] = 'なし'
all_data['remarks'].unique()
Out[19]:
array(['なし', 'お楽しみメニュー'], dtype=object)

欠損が埋まったか確認

In [20]:
all_data.info()
<class 'pandas.core.frame.DataFrame'>
Index: 247 entries, 2013-11-18 to 2014-11-28
Data columns (total 12 columns):
y                207 non-null float64
week             247 non-null object
soldout          247 non-null int64
name             247 non-null object
kcal             247 non-null float64
remarks          247 non-null object
event            247 non-null object
payday           247 non-null float64
weather          247 non-null object
precipitation    247 non-null object
temperature      247 non-null float64
flg              247 non-null int64
dtypes: float64(4), int64(2), object(6)
memory usage: 35.1+ KB

特徴量の作成

◆precipitation(降水量)
'--'という文字列と数値が混在しているため、
このままでは量的変数にもカテゴリ変数にも使えない

In [21]:
all_data[['weather', 'precipitation']].tail(30)
Out[21]:
weather precipitation
datetime
2014-10-16 晴れ --
2014-10-17 快晴 --
2014-10-20 薄曇 --
2014-10-21 0
2014-10-22 0
2014-10-23 0.5
2014-10-24 快晴 --
2014-10-27 薄曇 --
2014-10-28 快晴 --
2014-10-29 快晴 --
2014-10-30 晴れ --
2014-10-31 --
2014-11-4 晴れ --
2014-11-5 --
2014-11-6 --
2014-11-7 晴れ --
2014-11-10 快晴 --
2014-11-11 0
2014-11-12 --
2014-11-13 快晴 --
2014-11-14 快晴 --
2014-11-17 薄曇 --
2014-11-18 快晴 --
2014-11-19 快晴 --
2014-11-20 --
2014-11-21 快晴 --
2014-11-25 1.5
2014-11-26 1
2014-11-27 快晴 --
2014-11-28 --

・カテゴリ変数'precipitation_flg'を作る

降水量が--の時、「快晴、曇、晴れ、薄曇」→「天気が良い」とみなし'良い'で埋める
降水量が0の時、「雨、曇、雷電、薄曇」→小雨等「あまり天気が良くない」とみなし'やや悪い'で埋める
降水量に数値が入っている時、「曇、雨、雪」→「天気が悪い」とみなし'悪い'で埋める

In [22]:
# 降水量が--の時
all_data[all_data['precipitation'] == "--"]['weather'].unique()
Out[22]:
array(['快晴', '曇', '晴れ', '薄曇'], dtype=object)
In [23]:
# 降水量が0の時
all_data[all_data['precipitation'] == "0"]['weather'].unique()
Out[23]:
array(['雨', '曇', '雷電', '薄曇'], dtype=object)
In [24]:
# 降水量に数値が入っている時
all_data[~(all_data['precipitation'] == '0') & ~(all_data['precipitation'] == '--')]['weather'].unique()
Out[24]:
array(['曇', '雨', '雪'], dtype=object)
In [25]:
# precipitation_flg(カテゴリ変数)
all_data['precipitation_flg'] = '悪い'
all_data.loc[(all_data['precipitation'] == '--'), 'precipitation_flg'] = '良い'
all_data.loc[(all_data['precipitation'] == '0'), 'precipitation_flg'] = 'やや悪い'

・'--'を数値に変換して量的変数として使う

In [26]:
# 文字列を数値データに変える(量的変数)
all_data['precipitation'] = all_data['precipitation'].replace('--', '-1')

◆経過日数カラム
お弁当の売り上げ分布の確認
日数が経過するにつれて売り上げが落ちていく傾向=線形な関係がある
ただし所々急増するところがあるため為、売り上げ数に寄与している何かしらの要因がある

In [27]:
train["y"].plot(figsize=(15,4))
Out[27]:
<matplotlib.axes._subplots.AxesSubplot at 0x1fe69afb198>

日付の経過と関係が深いため、連番の数値を入れた経過日数カラムを作る

In [28]:
# 経過日数カラムを作成
all_data['days'] = range(1,len(all_data)+1)

One-Hotエンコ―ディング

カテゴリデータをOhe-Hot表現にする。
適用出来るカラムは
・一意の値が少ないもの
・カテゴリ変数であるもの(=量的変数でないもの)
「曜日,売り切れフラグ,特記事項,イベント,給料日,天気,降水量フラグ」をOne-Hotする

In [29]:
# 各カラムの一意の値を見る
for col in all_data.columns:
    print(col,all_data[col].unique())
    print(all_data[col].nunique())
    print('----------------------------------------------')
y [ 90. 101. 118. 120. 130. 135. 145. 140. 151. 116. 153. 171. 134. 165.
 155. 157. 109. 111. 160. 122. 121.  80. 131. 128. 129.  87. 107.  85.
 126.  92. 105. 139.  91. 137.  84.  86.  99. 113. 104.  69. 100. 110.
  77.  73. 123.  89.  68.  70.  72. 102.  51.  55. 106. 125.  96.  88.
  82.  57.  56.  78.  63.  54.  97.  64.  47.  59.  58.  71.  65.  79.
  74. 119.  93.  50.  76.  66.  61.  49.  53.  48. 124.  62.  83.  52.
  60.  38.  75.  45.  40.  39.  43. 115.  46.  29.  nan]
94
----------------------------------------------
week ['月' '火' '水' '木' '金']
5
----------------------------------------------
soldout [0 1]
2
----------------------------------------------
name ['厚切りイカフライ' '手作りヒレカツ' '白身魚唐揚げ野菜あん' '若鶏ピリ辛焼' 'ビッグメンチカツ' '鶏の唐揚' '豚のスタミナ炒め'
 'ボローニャ風カツ' 'ハンバーグ' 'タルタルinソーセージカツ' 'マーボ豆腐' '厚揚げ豚生姜炒め' 'クリームチーズ入りメンチ'
 '鶏のカッシュナッツ炒め' '手作りロースカツ' 'ハンバーグデミソース' 'やわらかロースのサムジョン' '五目御飯' '肉じゃが'
 'タンドリーチキン' 'カキフライタルタル' '回鍋肉' 'ポーク味噌焼き' '鶏の唐揚げ甘酢あん' 'さっくりメンチカツ'
 '手ごね風ハンバーグ' '酢豚' 'カレー入りソーセージカツ' '豚肉の生姜焼' '鶏チリソース' '鶏の照り焼きマスタード' 'さんま辛味焼'
 'カレイ唐揚げ野菜あんかけ' 'ジューシーメンチカツ' 'サバ焼味噌掛け' '手作りひれかつとカレー' '鶏のレモンペッパー焼orカレー'
 'チンジャオロース' '海老フライタルタル' 'チーズ入りメンチカツ' '鶏の唐揚げ' 'メダイ照り焼' 'ハンバーグカレーソース'
 'さわら焼味噌掛け' '鶏のピリ辛焼き' 'ホタテクリ―ムシチュー' '鶏の唐揚げおろしソース' 'ますのマスタードソース' 'ロース甘味噌焼き'
 '海老フライとホタテ串カツ' 'ハンバーグ和風きのこソース' '酢豚orカレー' 'ポークハヤシ' '白身魚唐揚げ野菜あんかけ'
 '手作りひれかつ' 'メンチカツ' 'チキンクリームシチュー' '海老クリーミ―クノーデル' 'ビーフカレー' 'カレイ野菜あんかけ'
 'チーズ入りハンバーグ' '越冬キャベツのメンチカツ' '鶏の親子煮' '肉団子クリームシチュー' 'キーマカレー' '青椒肉絲'
 '和風ソースハンバーグ' '青梗菜牛肉炒め' '肉団子のシチュー' 'チキンカレー' 'ビーフトマト煮' 'ポーク生姜焼き' '牛丼風煮'
 '鶏の味噌漬け焼き' '牛肉筍煮' '鶏の照り焼きマヨ' '中華丼' '豚味噌メンチカツ' 'マーボ茄子' '鶏の天ぷら' '手作りチキンカツ'
 'きのこソースハンバーグ' '白身魚唐揚げ野菜餡かけ' 'ポークカレー' '豚肉と茄子のピリ辛炒め' 'チーズハンバーグ'
 'サーモンのムニエル2色ソース' '牛肉コロッケ' '牛肉すき焼き風' 'いか天ぷら' 'ハンバーグケッチャップソース' 'ゴーヤチャンプルー'
 'たっぷりベーコンフライ' '牛肉ニンニクの芽炒め' 'カレイ唐揚げ野菜餡かけ' 'チャプチェ' '牛すき焼き風' 'ポークソテー韓国ソース'
 'ビーフストロガノフ' 'アジ唐揚げ南蛮ソース' '炊き込みご飯' '鶏のトマトシチュー' '豚の冷しゃぶ' 'キスと野菜の天ぷら' '牛丼'
 '鶏の塩から揚げ' 'カレイ唐揚げ夏野菜あん' '白身魚ムニエル' '手作りトンカツ' '和風ハンバーグ' 'かじきの甘辛煮'
 'チキンのコーンクリーム焼き' 'プルコギ' '鶏のから揚げねぎ塩炒めソース' '豚冷シャブ野菜添え' '白身魚フライ' '豚すき焼き'
 'エビフライ' '八宝菜' 'ジャンボチキンカツ' 'ひやしたぬきうどん・炊き込みご飯' '豚肉のマスタード焼き' 'バーベキューチキン'
 '鶏のから揚げスイートチリソース' '豚肉の生姜焼き' 'ハンバーグ(デミきのこバター)' '鶏肉のカレー唐揚' '豚キムチ炒め'
 'チキン香草焼きマスタードソース' 'サーモンフライ・タルタル' '厚切ハムカツ' '洋食屋さんのメンチカツ' '牛スキヤキ'
 '豚ロースのピザ風チーズ焼き' 'チキン南蛮' 'ロコモコ丼' '白身魚の南部焼き' 'カレイの唐揚げ' '豚肉の胡麻シャブ'
 'チキンの辛味噌焼き' 'ビーフシチュー' '名古屋味噌カツ' '親子煮' 'チキンステーキ・きのこソース' '鶏肉の山賊焼き'
 'ぶりレモンペッパー焼き' 'チーズメンチカツ' 'チキンフリカッセ' 'カレイ唐揚げ 甘酢あん' '厚切イカフライ' '筑前煮'
 '白身魚のマスタード焼き' '牛カルビ焼き肉' 'ランチビュッフェ' '豚肉と玉子の炒め' '鶏肉とカシューナッツ炒め' '麻婆春雨'
 '厚揚げ肉みそ炒め' '完熟トマトのホットカレー' '若鶏梅肉包揚げ' 'ミックスグリル' '豚肉と白菜の中華炒め' 'ヒレカツ' '豚柳川'
 '麻婆豆腐' '唐揚げ丼' 'マス塩焼き' '鶏肉と野菜の黒胡椒炒め' '彩り野菜と鶏肉の黒酢あん' 'ポークのバーベキューソテー'
 '鶏肉の唐揚げ' '白身魚味噌焼き' 'エビフライ・エビカツ' '野菜ごろごろシチュー' 'ベルギー風チキンのクリーム煮' 'スタミナ炒め'
 'なすと挽肉のはさみ揚げ' '鶏肉の治部煮風' '牛丼風' '鶏肉のスイートチリソース']
180
----------------------------------------------
kcal [404.40963855 404.         462.         435.         440.
 376.         450.         415.         430.         375.
 447.         426.         400.         418.         445.
 448.         460.         420.         425.         423.
 438.         432.         416.         405.         410.
 408.         396.         384.         436.         428.
 407.         413.         393.         409.         385.
 397.         370.         372.         360.         382.
 392.         350.         315.         349.         377.
 380.         390.         387.         353.         362.
 340.         330.         333.         325.         342.
 368.         434.         403.         406.         414.
 424.         417.         427.         421.         363.
 412.         354.         388.         386.         324.
 401.         355.         449.         441.         411.
 398.         395.         394.         433.        ]
79
----------------------------------------------
remarks ['なし' 'お楽しみメニュー']
2
----------------------------------------------
event ['なし' 'ママの会' 'キャリアアップ支援セミナー']
3
----------------------------------------------
payday [0. 1.]
2
----------------------------------------------
weather ['快晴' '曇' '晴れ' '薄曇' '雨' '雪' '雷電']
7
----------------------------------------------
precipitation ['-1' '0.5' '0' '1.5' '1' '6' '6.5' '2.5']
8
----------------------------------------------
temperature [19.8 17.  15.5 15.2 16.1 14.6 17.9 14.7 17.7 12.1 13.8 13.9 13.5 14.9
  9.1  7.1 11.  12.3 11.3 10.8  7.8  6.8  8.4 11.5  8.   7.3  9.8 10.9
  5.1  5.8  2.9  7.   6.5 10.4  7.4  8.5 10.1 12.6 10.3 15.3 14.1 15.9
  3.8  3.   4.2  5.5  6.3  6.4  1.2 11.6  5.4  6.6 10.5  8.3  6.1  6.7
  7.6  8.7 14.8 14.  18.9  7.5 17.4 18.7 12.4 18.5 17.5 18.2 14.3 17.8
 20.1 19.  21.3 16.5 16.4 19.7 21.5 11.4 18.8 19.4 19.9 20.8 22.7 24.
 23.3 21.  23.4 18.6 26.  19.3 23.8 22.8 23.9 23.7 25.7 26.9 30.6 27.
 27.5 25.  25.5 22.3 29.5 28.1 27.6 21.9 26.5 25.2 25.4 28.3 28.8 22.1
 29.7 24.6 33.9 30.9 30.3 30.1 25.6 30.2 32.  32.5 33.6 28.9 30.4 32.7
 34.3 34.6 32.1 31.1 32.4 29.4 32.2 33.3 33.  33.1 26.2 21.6 25.1 29.1
 26.7 23.1 26.8 27.3 24.1 23.6 24.8 27.1 26.6 20.2 28.7 22.5 26.1 15.6
 23.  19.1 19.6 16.  19.2 21.2 14.4 19.5 15.8  9.2 15.1]
165
----------------------------------------------
flg [1 0]
2
----------------------------------------------
precipitation_flg ['良い' '悪い' 'やや悪い']
3
----------------------------------------------
days [  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36
  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54
  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72
  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90
  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 106 107 108
 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
 235 236 237 238 239 240 241 242 243 244 245 246 247]
247
----------------------------------------------
In [30]:
all_data[['week']].head(10)
Out[30]:
week
datetime
2013-11-18
2013-11-19
2013-11-20
2013-11-21
2013-11-22
2013-11-25
2013-11-26
2013-11-27
2013-11-28
2013-11-29
In [31]:
# One-Hotエンコーダー
ohe_columns = ['week','soldout','remarks','event','payday','weather','precipitation_flg']

all_data = pd.get_dummies(all_data, dummy_na=False, columns=ohe_columns)
In [32]:
all_data[['week_月','week_火','week_水','week_木','week_金',]].head(10)
Out[32]:
week_月 week_火 week_水 week_木 week_金
datetime
2013-11-18 1 0 0 0 0
2013-11-19 0 1 0 0 0
2013-11-20 0 0 1 0 0
2013-11-21 0 0 0 1 0
2013-11-22 0 0 0 0 1
2013-11-25 1 0 0 0 0
2013-11-26 0 1 0 0 0
2013-11-27 0 0 1 0 0
2013-11-28 0 0 0 1 0
2013-11-29 0 0 0 0 1

モデル作成

trainデータとtestデータにわける

In [33]:
# trainとtestにわける
train = all_data.query('flg == 1')
test = all_data.query('flg == 0')

不要なカラムを捨てる

捨てるカラム
・結合した際testデータに作られたyカラム
・結合に使ったflgカラム
・一意の値が多く、One-Hot出来ないnameカラム

In [34]:
train.drop(['flg','name'], axis=1,inplace=True)
test.drop(['flg','name','y'], axis=1,inplace=True)
C:\Users\nakatsugawa.yui\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\core\frame.py:3694: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  errors=errors)
In [35]:
train.head()
Out[35]:
y kcal precipitation temperature days week_月 week_木 week_水 week_火 week_金 soldout_0 soldout_1 remarks_お楽しみメニュー remarks_なし event_なし event_キャリアアップ支援セミナー event_ママの会 payday_0.0 payday_1.0 weather_快晴 weather_晴れ weather_曇 weather_薄曇 weather_雨 weather_雪 weather_雷電 precipitation_flg_やや悪い precipitation_flg_悪い precipitation_flg_良い
datetime
2013-11-18 90.0 404.409639 -1 19.8 1 1 0 0 0 0 1 0 0 1 1 0 0 1 0 1 0 0 0 0 0 0 0 0 1
2013-11-19 101.0 404.409639 -1 17.0 2 0 0 0 1 0 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0 0 0 0 1
2013-11-20 118.0 404.409639 -1 15.5 3 0 0 1 0 0 1 0 0 1 1 0 0 1 0 1 0 0 0 0 0 0 0 0 1
2013-11-21 120.0 404.409639 -1 15.2 4 0 1 0 0 0 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0 0 0 0 1
2013-11-22 130.0 404.409639 -1 16.1 5 0 0 0 0 1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0 0 0 0 1

線形回帰モデルの作成(LinearRegression)

◆線形回帰とは
目的変数と説明変数の関係を直線で表せるとみなした回帰モデル

In [36]:
display_png(Image("./pic/linear.png", width=300, height=200,))

・説明変数と目的変数を分ける
・説明変数に使うカラムを厳選する
※チュートリアルでは'days'しか説明変数にしていない。
今回は徐々にカラムを増やし効果のあるものだけを追加した。

In [37]:
# trainデータの説明変数と目的変数をわける
linear_X_train = train[["days", "temperature", "kcal", "remarks_お楽しみメニュー", "remarks_なし"
                       ,"week_月", "week_火", "week_水", "week_木", "week_金", "weather_雨","soldout_1"
                      ,"precipitation_flg_やや悪い","precipitation_flg_悪い"]]

# 目的変数はLinear,RandomForest共通
y_train = train['y']

# testデータもlinear_X_trainと同じカラムにする
linear_test = test[["days", "temperature", "kcal", "remarks_お楽しみメニュー", "remarks_なし"
                 ,"week_月", "week_火", "week_水", "week_木", "week_金", "weather_雨","soldout_1"
                ,"precipitation_flg_やや悪い","precipitation_flg_悪い"]]

LinearRegressionのパラメータ設定
今回はチュートリアルに沿って、デフォルトに設定

In [38]:
# LinearRegressionのパラメータ設定
linear = LinearRegression()

モデルの作成

In [39]:
# 線形回帰モデル作成
linear.fit(linear_X_train,y_train)

# 予測
pred = linear.predict(linear_X_train)

RMSE算出
sklearnにはRMSEが実装されていない為、MSEにルートをとり、導出する

In [40]:
# y_trainと予測値のRMSEを出す
print("RMSE",np.sqrt(MSE(y_train,pred)))

# 線形回帰の予測値と実数値のグラフ
p = pd.DataFrame({"actual":y_train,"pred":pred})
p.plot(figsize=(15,4))
RMSE 16.18375891560382
Out[40]:
<matplotlib.axes._subplots.AxesSubplot at 0x1fe69a1b278>

アンサンブル学習(RandomForestRegressor)

LinearRegressionで予測した結果の残差を目的変数として学習し、
予測値を補正する為の非線形なモデルとしてRandomForestを使う。

◆RandomForestとは
複数の決定木からできたアルゴリズム

In [41]:
display_png(Image("./pic/RF_1.png", width=300, height=200,))

回帰では、それぞれの結果の平均を取る

In [42]:
display_png(Image("./pic/RF_2.png", width=300, height=200,))

◆パラメータ
・n_estimators=木の数(デフォルト:10)
・max_depth=木の深さ(デフォルト:なし)
・random_state=シード値を固定
今回はチュートリアルと同じ数値に設定

In [43]:
# RandomForestのパラメータ設定
randomforest = RandomForestRegressor(n_estimators=100,max_depth=4,random_state=2018)

# 残差 = 実測値 - 予測値 を求める
pred_sub = y_train - pred

# RandomForestの説明変数には全てのカラムを使う
randomforest_X_train = train.drop(['y'],axis=1)

# randomforest_X_trainを説明変数、pred_sub(残差)を目的変数にし、モデル作成
randomforest.fit(randomforest_X_train,pred_sub)
Out[43]:
RandomForestRegressor(bootstrap=True, criterion='mse', max_depth=4,
           max_features='auto', max_leaf_nodes=None,
           min_impurity_decrease=0.0, min_impurity_split=None,
           min_samples_leaf=1, min_samples_split=2,
           min_weight_fraction_leaf=0.0, n_estimators=100, n_jobs=1,
           oob_score=False, random_state=2018, verbose=0, warm_start=False)
In [44]:
# 線形回帰の予測値に、RandomForestで予測した残差を足し合わせたものを、最終的な予測値とする
pred = linear.predict(linear_X_train) + randomforest.predict(randomforest_X_train)

RMSE評価

In [45]:
# アンサンブル学習後のRMSE
print("RMSE",np.sqrt(MSE(y_train,pred)))

# アンサンブル学習後の予測値と実数値のグラフ
p = pd.DataFrame({"actual":y_train,"pred":pred})
p.plot(figsize=(15,4))
RMSE 10.88608764157751
Out[45]:
<matplotlib.axes._subplots.AxesSubplot at 0x1fe69e8f8d0>